package com.sne.common.util;

import com.alibaba.fastjson.JSONObject;
import com.sne.common.constant.RedisConstant;
import com.sne.common.entity.RedisUserInfo;
import com.sne.sys.service.ISysUserService;
import com.sne.sys.vo.SysUserVO;
import io.jsonwebtoken.Claims;
import io.jsonwebtoken.CompressionCodecs;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.stereotype.Component;

import java.time.LocalDateTime;
import java.util.*;
import java.util.concurrent.TimeUnit;

/**
 * <p>
 * jwt 工具类
 * </p>
 *
 * @author fanghh
 * @since 2020/1/6
 */
@Slf4j
@Component
public class JwtUtil {
//        public static final String ROLE_REFRESH_TOKEN = "ROLE_REFRESH_TOKEN";

    private static final String CLAIM_KEY_NICK_NAME = "nickName";

//        private static final String CLAIM_KEY_USER_ID = "user_id";
//        private static final String CLAIM_KEY_AUTHORITIES = "scope";

    @Autowired
    private StringRedisTemplate redisTemplate;

    @Value("${jwt.secret}")
    private String secret;

    @Value("${jwt.expiration}")
    private Long accessTokenExpiration;

    @Value("${jwt.expiration}")
    private Long refreshTokenExpiration;

    @Autowired
    private ISysUserService iSysUserService;

    private final SignatureAlgorithm SIGNATURE_ALGORITHM = SignatureAlgorithm.HS256;

    public SysUserVO getUserFromToken(String token) {
        SysUserVO userVO;
        try {
            Claims claims = getClaimsFromToken(token);
            String username = claims.getSubject();
            userVO = iSysUserService.findByName(username);
            return userVO;
        } catch (Exception e) {
            log.error(e.getMessage());
        }
        return null;
    }

    public String getUsernameFromToken(String token) {
        String username;
        try {
            Claims claims = getClaimsFromToken(token);
            username = claims.getSubject();
        } catch (Exception e) {
            username = null;
        }
        return username;
    }

    public Date getCreatedDateFromToken(String token) {
        Date created;
        try {
            final Claims claims = getClaimsFromToken(token);
            created = claims.getIssuedAt();
        } catch (Exception e) {
            created = null;
        }
        return created;
    }


    public String generateAccessToken(String userName) {
        Map<String, Object> claims = generateClaims(userName);
        return generateAccessToken(userName, claims);
    }

    public Date getExpirationDateFromToken(String token) {
        Date expiration;
        try {
            final Claims claims = getClaimsFromToken(token);
            expiration = claims.getExpiration();
        } catch (Exception e) {
            expiration = null;
        }
        return expiration;
    }

    public Boolean canTokenBeRefreshed(String token, Date lastPasswordReset) {
        final Date created = getCreatedDateFromToken(token);
        return !isCreatedBeforeLastPasswordReset(created, lastPasswordReset)
                && (!isTokenExpired(token));
    }

    public String refreshToken(String token) {
        String refreshedToken;
        try {
            final Claims claims = getClaimsFromToken(token);
            refreshedToken = generateAccessToken(claims.getSubject(), claims);
        } catch (Exception e) {
            refreshedToken = null;
        }
        return refreshedToken;
    }

    public Boolean validateToken(String token, UserDetails userDetails) {
        String username = getUsernameFromToken(token);
        boolean flag = Optional.ofNullable(redisTemplate.hasKey(RedisConstant.TOKEN + token)).orElse(false) ;
        return (username.equals(userDetails.getUsername()) && flag
                && !isTokenExpired(token)
        );
    }

    public String generateRefreshToken(String userName) {
        Map<String, Object> claims = generateClaims(userName);
        return generateRefreshToken(userName, claims);
    }

    public void putToken(String userName, String token) {
        RedisUserInfo userInfo = new RedisUserInfo();
        userInfo.setUserName(userName).setCreateTime(LocalDateTime.now());
        redisTemplate.opsForValue().set(RedisConstant.TOKEN+token,JSONObject.toJSONString(userInfo),
                accessTokenExpiration, TimeUnit.SECONDS);
    }

    public void deleteToken(String token) {
        redisTemplate.delete(RedisConstant.TOKEN+token);
    }

    public boolean containToken(String userName) {
        return userName != null && redisTemplate.opsForHash().hasKey(RedisConstant.TOKEN, userName);
    }

    private Claims getClaimsFromToken(String token) {
        Claims claims;
        try {
            claims = Jwts.parser()
                    .setSigningKey(secret)
                    .parseClaimsJws(token)
                    .getBody();
        } catch (Exception e) {
            claims = null;
        }
        return claims;
    }

    private Date generateExpirationDate(long expiration) {
        return new Date(System.currentTimeMillis() + expiration * 1000);
    }

    private Boolean isTokenExpired(String token) {
        final Date expiration = getExpirationDateFromToken(token);
        return expiration.before(new Date());
    }

    private Boolean isCreatedBeforeLastPasswordReset(Date created, Date lastPasswordReset) {
        return (lastPasswordReset != null && created.before(lastPasswordReset));
    }

    private Map<String, Object> generateClaims(String userName) {
        Map<String, Object> claims = new HashMap<>(16);
        SysUserVO userVO = iSysUserService.findByName(userName);
        claims.put(CLAIM_KEY_NICK_NAME, userVO.getNickName());
        return claims;
    }

    private String generateAccessToken(String subject, Map<String, Object> claims) {
        return generateToken(subject, claims, accessTokenExpiration);
    }


    private String generateRefreshToken(String subject, Map<String, Object> claims) {
        return generateToken(subject, claims, refreshTokenExpiration);
    }

    private String generateToken(String subject, Map<String, Object> claims, long expiration) {
        return Jwts.builder()
                .setClaims(claims)
                .setSubject(subject)
                .setId(UUID.randomUUID().toString())
                .setIssuedAt(new Date())
                .setExpiration(generateExpirationDate(expiration))
                .compressWith(CompressionCodecs.DEFLATE)
                .signWith(SIGNATURE_ALGORITHM, secret)
                .compact();
    }

}

